<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .container {
            width: 360px;
            aspect-ratio: 16 / 9;
            overflow: hidden;
        }

        .wrap {
            position: relateive;
            width: 100%;
        }

        .img {
            width: 100%;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="wrap">
            <img src="./test.png" alt="" class="img">
        </div>
    </div>
</body>
<script>
    const useZoom = (el) => {
        el.style.transformOrigin = '0 0'
        const { width, height } = el.getBoundingClientRect()
        let translate = {
            x: 0,
            y: 0
        }
        let scale = 1

        const updateView = () => {
            el.style.transform = `translate(${translate.x}px, ${translate.y}px) scale(${scale})`
        }

        const reset = () => {
            translate = {
                x: 0,
                y: 0
            }
            scale = 1
            updateView()
        }

        const calcTranslateDelta = ({ offsetX, offsetY }, newScale, oldScale) => {
            const oldWidth = width * oldScale
            const oldHeight = height * oldScale
            const diffWidth = oldWidth - newScale * width
            const diffHeight = oldHeight - newScale * height

            const ratioX = offsetX / width
            const ratioY = offsetY / height

            return {
                deltaX: ratioX * diffWidth,
                deltaY: ratioY * diffHeight
            }
        }

        const wheelZoom = (event) => {
            let _scale = scale
            _scale += event.deltaY < 0 ? -0.09 : 0.1
            if (_scale < 1) {
                reset()
                return
            }

            const { deltaX, deltaY } = calcTranslateDelta(event, _scale, scale)
            translate.x += deltaX
            translate.y += deltaY
            scale = _scale
            updateView()
        }

        return {
            wheelZoom
        }
    }

    const useAddSpur = (el) => {

        const createSpan = () => {
            const span = document.createElement('span')
            const spanStyle = span.style
            spanStyle.position = 'absolute'
            spanStyle.width = '10px'
            spanStyle.height = '10px'
            spanStyle.borderRadius = '50%'
            spanStyle.background = 'red'

            const setStyle = (key, value) => {
                spanStyle[key] = value
            }

            return [span, setStyle]
        }

        const addSpur = (event) => {
            const { offsetX, offsetY } = event
            const [span, setStyle] = createSpan()
            setStyle('left', `${offsetX}px`)
            setStyle('top', `${offsetY}px`)
            el.appendChild(span)
        }

        return { addSpur }
    }

</script>
<script>
    const container = document.querySelector('.container')
    const wrap = document.querySelector('.wrap')
    const img = document.querySelector('.img')

    const { wheelZoom } = useZoom(wrap)
    const { addSpur } = useAddSpur(wrap)

    container.addEventListener('wheel', wheelZoom)
    wrap.addEventListener('click', addSpur)
</script>

</html>